//
//  Material.cpp
//  RenderV1
//
//  Created by 黄翔 on 2019/10/20.
//  Copyright © 2019 黄翔. All rights reserved.
//

#include "Material.h"
#include "render_v1.h"

using namespace std;

void hxe::Material::prepareDraw()
{
    if (cull > 0)
    {
        glEnable(GL_CULL_FACE);
        glCullFace(cull);
    } else
    {
        glDisable(GL_CULL_FACE);
    }

    if (depthTest > 0)
    {
        glEnable(GL_DEPTH_TEST);
    }
    else
    {
        glDisable(GL_DEPTH_TEST);
    }

    if (blend > 0)
    {
        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    }
    else
    {
        glDisable(GL_BLEND);
    }

    glPolygonMode(GL_FRONT_AND_BACK, this->polygonMode);

    shader->use();
    for (unsigned int i = 0; i < textures.size(); i++)
    {
        glActiveTexture(GL_TEXTURE0 + i);
        glBindTexture(textures[i]->getBindType(), textures[i]->getId());

        shader->setInt(textures[i]->getType().c_str(), i);
    }

    for (unsigned int i = 0; i < properties.size(); i++)
    {
        Property* p = properties[i];
        std::string name = p->getName();
        switch (p->getPropertyType())
        {
        case PROPERTY_BOOL:
            shader->setBool(name, p->getBool());
            break;
        case PROPERTY_INT:
            shader->setInt(name, p->getInt());
            break;
        case PROPERTY_FLOAT:
            shader->setFloat(name, p->getFloat());
            break;
        case PROPERTY_VECTOR_3:
            shader->setVector3(name, p->getVector3());
            break;
        case PROPERTY_VECTOR_4:
            shader->setVector4(name, p->getVector4());
            break;
        case PROPERTY_MATRIX_4X4:
            shader->setMatrix(name, p->getMatrix());
            break;
        default:
            break;
        }
    }
}


hxe::Shader* hxe::Material::getShader() const
{
    return shader;
}

hxe::Material::Material(Shader* shader, std::vector<Texture*> textures):Material(shader)
{
    this->textures = textures;
}


hxe::Material::Material(Material* source)
{
    this->polygonMode = source->polygonMode;
    this->cull = source->cull;
    this->blend = source->blend;
    this->depthTest = source->depthTest;
    this->shader = source->shader;
    this->shader->grab();

    for (unsigned int i = 0; i < source->textures.size(); i++)
    {
        Texture* texture = source->textures[i];
        texture->grab();
        textures.push_back(texture);
    }

    for (unsigned int i = 0; i < source->properties.size(); i++)
    {
        Property* p = (source->properties[i])->copy();
        properties.push_back(p);
    }
}

hxe::Material::Material()
{
    this->polygonMode = GL_FILL;
    this->cull = GL_BACK;
    this->blend = 0;
    this->depthTest = 1;
}

hxe::Material::Material(std::string path):Material()
{
    std::ifstream matFile;
    matFile.open(path);
    std::string line;
    if (!matFile.is_open())
    {
        std::string s = path + " not find";
        LoggerInst->print(s.c_str());
        return;
    }

    while (!matFile.eof())
    {
        getline(matFile, line);
        if (line.empty())
        {
            continue;
        }
        Utils::trimString(line);

        parse:
        vector<string> v = Utils::splitString(line, " ");
        string tag = v[0];
        if (tag == "shader:")
        {
            shader = AssetDatabaseInst->loadAssetAtPath<Shader>(v[1], v[2]);
        }
        else if (tag == "polygonMode:")
        {
            string mode = v[1];
            if (mode == "line")
            {
                polygonMode = GL_LINE;
            }
            else if (mode == "point")
            {
                polygonMode = GL_POINT;
            }
            else
            {
                polygonMode = GL_FILL;
            }
        }
        else if (tag == "texture:")
        {
            string type = v[1];
            string path = v[2];
            Texture* texture = AssetDatabaseInst->loadAssetAtPath<Texture>(path, type);
            textures.push_back(texture);
        }
        else if (tag == "cubeTexture:")
        {
            vector<string> faces;
            for (unsigned int i = 1; i <= 6; i++)
            {
                faces.push_back(v[i]);
            }
            Texture* texture = AssetDatabaseInst->loadAssetAtPath<Texture>(faces);
            textures.push_back(texture);
        }
        else if (tag == "cull:")
        {
            string mode = v[1];
            if (mode == "off")
            {
                cull = 0;
            } else if (mode == "front")
            {
                cull = GL_FRONT;
            } else
            {
                cull = GL_BACK;
            }
        }
        else if (tag == "blend:")
        {
            string mode = v[1];
            if (mode == "off")
            {
                blend = 0;
            }
            else if (mode == "on")
            {
                blend = 1;
            }
        }
        else if (tag == "depthTest:")
        {
            string mode = v[1];
            if (mode == "off")
            {
                depthTest = 0;
            }
            else if (mode == "on")
            {
                depthTest = 1;
            }
        }
        else if (tag == "property:")
        {
            line = handleProperty(matFile);
            if (line == "")
            {
                break;
            }

            goto parse;
        }
    }
    matFile.close();
}

string hxe::Material::handleProperty(std::ifstream& matFile)
{
    while (!matFile.eof())
    {
        std::string line;
        getline(matFile, line);
        if (line.empty())
        {
            continue;
        }
        Utils::trimString(line);

        vector<string> v = Utils::splitString(line, " ");
        string k = v[0];
        if (k != "-")
        {
            return line;
        }

        string propertyType = v[1];
        string name = v[2];
        Property* p = nullptr;

        if (propertyType == "bool:")
        {
            p = new BoolProperty(name);
            p->setInt(v[3] == "true" ? 0 : 1);
        }
        else if (propertyType == "int:")
        {
            p = new IntProperty(name);
            p->setInt(str2Int(v[3]));
        }
        else if (propertyType == "float:")
        {
            p = new FloatProperty(name);
            p->setFloat(str2Float(v[3]));
        }
        else if (propertyType == "vector3:")
        {
            p = new Vector3Property(name);
            p->setVector3(Vector3(str2Float(v[3]), str2Float(v[4]), str2Float(v[5])));
        }
        else if (propertyType == "vector4:")
        {
            p = new Vector4Property(name);
            p->setVector4(Vector4(str2Float(v[3]), str2Float(v[4]), str2Float(v[5]), str2Float(v[6])));
        }
        else 
        {
            string logStr = propertyType + " not define!";
            LoggerInst->print(logStr.c_str());
        }

        if (p != nullptr)
        {
            properties.push_back(p);
        }

    }
    return "";
}

hxe::Material::Material(Shader* shader):Material()
{
    this->shader = shader;
}

hxe::Material::~Material()
{
    shader->drop();
    for(unsigned int i = 0; i < textures.size(); i++)
    {
        textures[i]->drop();
    }

    for (unsigned int i = 0; i < properties.size(); i++)
    {
        properties[i]->drop();
    }
}

void hxe::Material::setBool(const std::string &name, bool value)
{
    Property* p = findProperty(name);
    if (p != nullptr)
    {
        p->setBool(value);
    }
    else
    {
        Property* bp = new BoolProperty(name);
        bp->setBool(value);
        properties.push_back(bp);
    }
}

void hxe::Material::setInt(const std::string &name, int value)
{
    Property* p = findProperty(name);
    if (p != nullptr)
    {
        p->setInt(value);
    }
    else
    {
        Property* ip = new IntProperty(name);
        ip->setInt(value);
        properties.push_back(ip);
    }
}

void hxe::Material::setFloat(const std::string &name, float value)
{
    Property* p = findProperty(name);
    if (p != nullptr)
    {
        p->setFloat(value);
    }
    else
    {
        Property* fp = new FloatProperty(name);
        fp->setFloat(value);
        properties.push_back(fp);
    }
}

void hxe::Material::setVector4(const std::string &name, const Vector4& v)
{
    Property* p = findProperty(name);
    if (p != nullptr)
    {
        p->setVector4(v);
    }
    else
    {
        Property* v4p = new Vector4Property(name);
        v4p->setVector4(v);
        properties.push_back(v4p);
    }

}

void hxe::Material::setVector3(const std::string &name, const Vector3& v)
{
    Property* p = findProperty(name);
    if (p != nullptr)
    {
        p->setVector3(v);
    }
    else
    {
        Property* v3p = new Vector3Property(name);
        v3p->setVector3(v);
        properties.push_back(v3p);
    }
}

void hxe::Material::setMatrix(const std::string &name, const Matrix4x4 &matrix)
{
    Property* p = findProperty(name);
    if (p != nullptr)
    {
        p->setMatrix(matrix);
    }
    else
    {
        Property* mp = new Matrix4x4Property(name);
        mp->setMatrix(matrix);
        properties.push_back(mp);
    }
}


void hxe::Material::addTexture(Texture* texture)
{
    textures.push_back(texture);
}

hxe::Property* hxe::Material::findProperty(const std::string &name)
{
    for (unsigned int i = 0; i < properties.size(); i++)
    {
        if (properties[i]->getName() == name)
        {
            return properties[i];
        }
    }

    return nullptr;
}


